// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/non_thread_safe.h"

#include <memory>

#include "base/logging.h"
#include "base/macros.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"

// Duplicated from base/threading/non_thread_safe.h so that we can be
// good citizens there and undef the macro.
#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
#define ENABLE_NON_THREAD_SAFE 1
#else
#define ENABLE_NON_THREAD_SAFE 0
#endif

namespace base {

namespace {

    // Simple class to exersice the basics of NonThreadSafe.
    // Both the destructor and DoStuff should verify that they were
    // called on the same thread as the constructor.
    class NonThreadSafeClass : public NonThreadSafe {
    public:
        NonThreadSafeClass() { }

        // Verifies that it was called on the same thread as the constructor.
        void DoStuff()
        {
            DCHECK(CalledOnValidThread());
        }

        void DetachFromThread()
        {
            NonThreadSafe::DetachFromThread();
        }

        static void MethodOnDifferentThreadImpl();
        static void DestructorOnDifferentThreadImpl();

    private:
        DISALLOW_COPY_AND_ASSIGN(NonThreadSafeClass);
    };

    // Calls NonThreadSafeClass::DoStuff on another thread.
    class CallDoStuffOnThread : public SimpleThread {
    public:
        explicit CallDoStuffOnThread(NonThreadSafeClass* non_thread_safe_class)
            : SimpleThread("call_do_stuff_on_thread")
            , non_thread_safe_class_(non_thread_safe_class)
        {
        }

        void Run() override { non_thread_safe_class_->DoStuff(); }

    private:
        NonThreadSafeClass* non_thread_safe_class_;

        DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread);
    };

    // Deletes NonThreadSafeClass on a different thread.
    class DeleteNonThreadSafeClassOnThread : public SimpleThread {
    public:
        explicit DeleteNonThreadSafeClassOnThread(
            NonThreadSafeClass* non_thread_safe_class)
            : SimpleThread("delete_non_thread_safe_class_on_thread")
            , non_thread_safe_class_(non_thread_safe_class)
        {
        }

        void Run() override { non_thread_safe_class_.reset(); }

    private:
        std::unique_ptr<NonThreadSafeClass> non_thread_safe_class_;

        DISALLOW_COPY_AND_ASSIGN(DeleteNonThreadSafeClassOnThread);
    };

} // namespace

TEST(NonThreadSafeTest, CallsAllowedOnSameThread)
{
    std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
        new NonThreadSafeClass);

    // Verify that DoStuff doesn't assert.
    non_thread_safe_class->DoStuff();

    // Verify that the destructor doesn't assert.
    non_thread_safe_class.reset();
}

TEST(NonThreadSafeTest, DetachThenDestructOnDifferentThread)
{
    std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
        new NonThreadSafeClass);

    // Verify that the destructor doesn't assert when called on a different thread
    // after a detach.
    non_thread_safe_class->DetachFromThread();
    DeleteNonThreadSafeClassOnThread delete_on_thread(
        non_thread_safe_class.release());

    delete_on_thread.Start();
    delete_on_thread.Join();
}

#if GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE

void NonThreadSafeClass::MethodOnDifferentThreadImpl()
{
    std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
        new NonThreadSafeClass);

    // Verify that DoStuff asserts in debug builds only when called
    // on a different thread.
    CallDoStuffOnThread call_on_thread(non_thread_safe_class.get());

    call_on_thread.Start();
    call_on_thread.Join();
}

#if ENABLE_NON_THREAD_SAFE
TEST(NonThreadSafeDeathTest, MethodNotAllowedOnDifferentThreadInDebug)
{
    ASSERT_DEATH({
        NonThreadSafeClass::MethodOnDifferentThreadImpl();
    },
        "");
}
#else
TEST(NonThreadSafeTest, MethodAllowedOnDifferentThreadInRelease)
{
    NonThreadSafeClass::MethodOnDifferentThreadImpl();
}
#endif // ENABLE_NON_THREAD_SAFE

void NonThreadSafeClass::DestructorOnDifferentThreadImpl()
{
    std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
        new NonThreadSafeClass);

    // Verify that the destructor asserts in debug builds only
    // when called on a different thread.
    DeleteNonThreadSafeClassOnThread delete_on_thread(
        non_thread_safe_class.release());

    delete_on_thread.Start();
    delete_on_thread.Join();
}

#if ENABLE_NON_THREAD_SAFE
TEST(NonThreadSafeDeathTest, DestructorNotAllowedOnDifferentThreadInDebug)
{
    ASSERT_DEATH({
        NonThreadSafeClass::DestructorOnDifferentThreadImpl();
    },
        "");
}
#else
TEST(NonThreadSafeTest, DestructorAllowedOnDifferentThreadInRelease)
{
    NonThreadSafeClass::DestructorOnDifferentThreadImpl();
}
#endif // ENABLE_NON_THREAD_SAFE

#endif // GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE

// Just in case we ever get lumped together with other compilation units.
#undef ENABLE_NON_THREAD_SAFE

} // namespace base
